﻿using System;
using System.Collections.Generic;

namespace BFlatCompile.Serializer
{
	public static class ByteCodes
	{
		private static readonly HashSet<string> registry = new HashSet<string>();
		private static readonly HashSet<string> readableRegistry = new HashSet<string>();

		private static readonly string[] CODE_ACTIVE_EXCEPTION = Reg("a", "acex"); // push the active exception onto the stack
		private static readonly string[] CODE_ADD = Reg("b", "addd"); // pop stack twice and add the values, push sum onto stack
		private static readonly string[] CODE_APPLY_ARG_N = Reg("c", "argn"); // one value: argument index from function call. Find that value and push it onto the stack.
		private static readonly string[] CODE_ARG_CHECK = Reg("d", "argc"); // n + 2 values or more. 2nd value is n. 3rd+ is a list of type ID's (in CodeProvider.cs). If the args passed in to the current stack frame match these values, proceed, otherwise skip the offset provided as the first number. If the nth arg is of type class, the n+1 th arg is the required class ID or 0 if no specific type is required. This can make the length longer than n.
		private static readonly string[] CODE_ARG_PRE_VERIFY = Reg("@", "arpv"); // occurs after an APPLY_ARG_N (which could be an implicit function call, thus why this functionality is not combined with APPLY_ARG_N). Takes the previous value on the value stack and applies it to the stack context's final arguments list. This final arguments list is used for the ARG_CHECK, since the actual arguments passed cannot be used. That list may be shorter and doesn't allow you to check optionally set args. Since these occur in order, the index of the arg is not necessary.
		private static readonly string[] CODE_ASSIGN = Reg("e", "asgn"); // Pop two value. The former is a pointer and will be assigned the value of the latter
		private static readonly string[] CODE_BASE_CLASS_CONS = Reg("f", "bccc"); // Call the constructor of the base class but with this class instance as the context. First param is the number of constructor args. Second param is the constructor ID to call.
		private static readonly string[] CODE_BITWISE_AND = Reg("g", "bita"); // pop twice, bitwise-and, push
		private static readonly string[] CODE_BITWISE_OR = Reg("h", "bito"); // pop twice, bitwise-or, push
		private static readonly string[] CODE_BITWISE_XOR = Reg("i", "bitx"); // pop twice, bitwise-xor, push
		private static readonly string[] CODE_BOOLEAN_NOT = Reg("j", "bnot"); // pop once, boolean not, push
		private static readonly string[] CODE_BRACKET = Reg("k", "brku"); // pop two values and bracket derference the lower with the higher (unwrapped)
		private static readonly string[] CODE_BRACKET_WRAPPED = Reg("l", "brkw"); // pop two values and store a pointer to the resulting value store, not the value itself (wrapped)
		private static readonly string[] CODE_CLASS = Reg("m", "clss"); // push a class value.
		private static readonly string[] CODE_CLASS_INIT_COMPLETE = Reg("n", "cini"); // Class initialization is complete. This appears at the end of the static initializer. Arg is class ID.
		private static readonly string[] CODE_CONSTRUCTOR_CALL = Reg("o", "cons"); // identical to FUNCTION_CALL, but root is a class definition and return value is overridden with a new instance of that class.
		private static readonly string[] CODE_CREATE_LIST = Reg("p", "list"); // has integer arg, pop that many items from stack and push a single list object
		private static readonly string[] CODE_CREATE_MAP = Reg("q", "mapc"); // has integer arg, pop that many items TIMES TWO from stack and create single map object. every other object is a key and the others are values.
		private static readonly string[] CODE_DIVIDE = Reg("r", "divd"); // pop stack twice, divide, push
		private static readonly string[] CODE_DOT = Reg("s", "dotu"); // pop value and call a dot dereference on it. (unwrapped)
		private static readonly string[] CODE_DOT_WRAPPED = Reg("t", "dotw"); // pop value and push a wrapped reference to a step location for later assignment or dereferencing. (wrapped)
		private static readonly string[] CODE_EQUALS = Reg("u", "eqls"); // pop two, apply ==, push result
		private static readonly string[] CODE_FALSE = Reg("v", "fals"); // push false onto the stack
		private static readonly string[] CODE_FLOAT = Reg("w", "flot"); // push a float constant onto the stack
		private static readonly string[] CODE_FUNCTION_CALL = Reg("x", "func"); // pop N params (n is the op param), pop function, assign values and default values accordingly to the function, set callback pc, push new stack info
		private static readonly string[] CODE_GREATER_THAN = Reg("y", "grtr"); // pop two, apply >, push result
		private static readonly string[] CODE_GREATER_THAN_OR_EQUAL = Reg("z", "greq"); // pop two, apply >=, push result
		private static readonly string[] CODE_INCREMENT_PREFIX = Reg("A", "ipre"); // ++ or -- as a prefix, requires a pointer in previous cell, does proper manipulations
		private static readonly string[] CODE_INCREMENT_POSTFIX = Reg("B", "ipst"); // ++ or -- as a postfix, requires a pointer in previous cell, does proper manipulations
		private static readonly string[] CODE_INSTANCE_FIELD_ASSIGN = Reg("C", "asni"); // param is field member ID. Assign to this field. 
		private static readonly string[] CODE_INTEGER = Reg("D", "intg"); // push an integer constant onto the stack
		private static readonly string[] CODE_INVOKE_STATIC_CONSTRUCTOR = Reg("9", "insc"); // invokes the static constructor of the given class ID
		private static readonly string[] CODE_JUMP = Reg("E", "jump"); // move program counter by x amount
		private static readonly string[] CODE_JUMP_IF_FALSE = Reg("F", "jmpf"); // pop statkc, if false, move program counter, otherwise do nothing
		private static readonly string[] CODE_JUMP_IF_FALSE_NO_POP = Reg("G", "jfnp"); // if last item on stack is false, jump. Do not pop the stack.
		
		// TODO: remove this op code
		private static readonly string[] CODE_JUMP_IF_NO_ARG_PRESENT = Reg("H", "jnap"); // two args: index and jump distance. If the nth arg is not part of the function call, jump the offset.
		private static readonly string[] CODE_JUMP_IF_TRUE = Reg("I", "jmpt"); // pop stack, if true, move program counter, otherwise do nothing
		private static readonly string[] CODE_JUMP_IF_TRUE_NO_POP = Reg("J", "jtnp"); // if last item on stack is true, jump. Do not pop the stack.
		private static readonly string[] CODE_KEEP_AND_DEREF_POINTER = Reg("K", "kadp"); // PEEK stack for pointer information, dereference value from pointer and push onto stack
		private static readonly string[] CODE_LESS_THAN = Reg("L", "less"); // pop two, apply <, push result
		private static readonly string[] CODE_LESS_THAN_OR_EQUAL = Reg("M", "lseq"); // pop two, apply <=, push result
		private static readonly string[] CODE_MOD = Reg("N", "mdlo"); // pop stack twice, modulo, push
		private static readonly string[] CODE_MULTIPLY = Reg("O", "mult"); // pop stack twice, multiply, push
		private static readonly string[] CODE_NATIVE_FUNCTION = Reg("P", "ntvf"); // param: ID# of the native function. Native function is passed the top stack frame and always pushes one return value on completion. 
		private static readonly string[] CODE_NEGATIVE = Reg("Q", "ngtv"); // pop stack, flip sign, push
		private static readonly string[] CODE_NOOP = Reg("R", "noop"); // do nothing.
		private static readonly string[] CODE_NOT_EQUAL = Reg("S", "nteq"); // pop two, apply !=, push result
		private static readonly string[] CODE_NULL = Reg("T", "null"); // push null onto stack
		private static readonly string[] CODE_POP = Reg("U", "popx"); // pop the stack and do nothing with it
		private static readonly string[] CODE_RETURN = Reg("V", "rtrn"); // pop value and return it
		private static readonly string[] CODE_STACK_DATA = Reg("W", "stdt"); // not executed. Used at end of each chunk of serialized code indicating what method this is. Used by exceptions to populate stack trace. First param is file name, second param is member signature. Both of these are ID#'s for string table entries.
		private static readonly string[] CODE_STRING = Reg("X", "stng"); // push a string from the string table onto the stack
		private static readonly string[] CODE_SUBTRACT = Reg("Y", "sbtr"); // pop stack twice and subtract (top of stack is subtracted from 2nd from top)
		private static readonly string[] CODE_SWITCH = Reg("Z", "swtc"); // switch statement contains a list of params to be compiled into a dictionary lookup upon loading the byte code. params are comma-delimited and end in |. Each pair of 2 params is a value and an offset. The value is either 'i' for integer or 's' for string and is immediately followed by a number indicating where in the lookup table this value is. The offset is the jump. The default statement is a '0' instead of 'i' or 's' and has no number after it.
		private static readonly string[] CODE_THIS = Reg("0", "this"); // push a this reference onto stack
		private static readonly string[] CODE_THROW = Reg("1", "thrw"); // pop the value on the stack, make sure it's an exception, and throw it
		private static readonly string[] CODE_THROW_BUBBLING = Reg("2", "trwb"); // find the currently caught exception in the top try-catch context in the current stack frame and throw that.
		private static readonly string[] CODE_TRUE = Reg("3", "true"); // push true onto stack
		private static readonly string[] CODE_TRY_POP = Reg("4", "trpo"); // see comments in SerializeTryBlock
		private static readonly string[] CODE_TRY_PUSH = Reg("5", "trps"); // see comments in SerializeTryBlock.
		private static readonly string[] CODE_VARIABLE = Reg("6", "varu"); // push value of a variable (unwrapped)
		private static readonly string[] CODE_VARIABLE_WRAPPED = Reg("7", "varw"); // push the location of a variable (wrapped) for the purposes of assignment and possibly dereferencing also.
		private static readonly string[] CODE_XOR = Reg("8", "xorr"); // pop two, xor, push result

		public static string ACTIVE_EXCEPTION(Compiler c) { return None(c, CODE_ACTIVE_EXCEPTION); }
		public static string ADD(Compiler c) { return None(c, CODE_ADD); }
		public static string APPLY_ARG_N(Compiler c, params int[] a) { return Args(c, CODE_APPLY_ARG_N, a); }
		public static string ARG_CHECK(Compiler c, params int[] a) { return Args(c, CODE_ARG_CHECK, a); }
		public static string ARG_PRE_VERIFY(Compiler c) { return None(c, CODE_ARG_PRE_VERIFY); }
		public static string ASSIGN(Compiler c) { return None(c, CODE_ASSIGN); }
		public static string BASE_CLASS_CONS(Compiler c, params int[] a) { return Args(c, CODE_BASE_CLASS_CONS, a); }
		public static string BITWISE_AND(Compiler c) { return None(c, CODE_BITWISE_AND); }
		public static string BITWISE_OR(Compiler c) { return None(c, CODE_BITWISE_OR); }
		public static string BITWISE_XOR(Compiler c) { return None(c, CODE_BITWISE_XOR); }
		public static string BOOLEAN_NOT(Compiler c) { return None(c, CODE_BOOLEAN_NOT); }
		public static string BRACKET(Compiler c) { return None(c, CODE_BRACKET); }
		public static string BRACKET_WRAPPED(Compiler c) { return None(c, CODE_BRACKET_WRAPPED); }
		public static string CLASS(Compiler c, int a) { return Single(c, CODE_CLASS, a); }
		public static string CLASS_INIT_COMPLETE(Compiler c, int a) { return Single(c, CODE_CLASS_INIT_COMPLETE, a); }
		public static string CREATE_LIST(Compiler c, int itemCount) { return Single(c, CODE_CREATE_LIST, itemCount); }
		public static string CREATE_MAP(Compiler c, int itemCount) { return Single(c, CODE_CREATE_MAP, itemCount); }
		public static string CONSTRUCTOR_CALL(Compiler c, int a) { return Single(c, CODE_CONSTRUCTOR_CALL, a); }
		public static string DIVIDE(Compiler c) { return None(c, CODE_DIVIDE); }
		public static string DOT(Compiler c, int a) { return Single(c, CODE_DOT, a); }
		public static string DOT_WRAPPED(Compiler c, int a) { return Single(c, CODE_DOT_WRAPPED, a); }
		public static string EQUALS(Compiler c) { return None(c, CODE_EQUALS); }
		public static string FALSE(Compiler c) { return None(c, CODE_FALSE); }
		public static string FLOAT(Compiler c, int a) { return Single(c, CODE_FLOAT, a); }
		public static string FUNCTION_CALL(Compiler c, int a) { return Single(c, CODE_FUNCTION_CALL, a); }
		public static string GREATER_THAN(Compiler c) { return None(c, CODE_GREATER_THAN); }
		public static string GREATER_THAN_OR_EQUAL(Compiler c) { return None(c, CODE_GREATER_THAN_OR_EQUAL); }
		public static string INTEGER(Compiler c, int a) { return Single(c, CODE_INTEGER, a); }
		public static string INCREMENT_POSTFIX(Compiler c, int a) { return Single(c, CODE_INCREMENT_POSTFIX, a); }
		public static string INCREMENT_PREFIX(Compiler c, int a) { return Single(c, CODE_INCREMENT_PREFIX, a); }
		public static string INVOKE_STATIC_CONSTRUCTOR(Compiler c, int classId) { return Single(c, CODE_INVOKE_STATIC_CONSTRUCTOR, classId); }
		public static string JUMP(Compiler c, int a) { return Single(c, CODE_JUMP, a); }
		public static string JUMP_IF_FALSE(Compiler c, int a) { return Single(c, CODE_JUMP_IF_FALSE, a); }
		public static string JUMP_IF_FALSE_NO_POP(Compiler c, int a) { return Single(c, CODE_JUMP_IF_FALSE_NO_POP, a); }
		public static string JUMP_IF_NO_ARG_PRESENT(Compiler c, params int[] a) { return Args(c, CODE_JUMP_IF_NO_ARG_PRESENT, a); }
		public static string JUMP_IF_TRUE(Compiler c, int a) { return Single(c, CODE_JUMP_IF_TRUE, a); }
		public static string JUMP_IF_TRUE_NO_POP(Compiler c, int a) { return Single(c, CODE_JUMP_IF_TRUE_NO_POP, a); }
		public static string KEEP_AND_DEREF_POINTER(Compiler c) { return None(c, CODE_KEEP_AND_DEREF_POINTER); }
		public static string LESS_THAN(Compiler c) { return None(c, CODE_LESS_THAN); }
		public static string LESS_THAN_OR_EQUAL(Compiler c) { return None(c, CODE_LESS_THAN_OR_EQUAL); }
		public static string MOD(Compiler c) { return None(c, CODE_MOD); }
		public static string MULTIPLY(Compiler c) { return None(c, CODE_MULTIPLY); }
		public static string NATIVE_FUNCTION(Compiler c, params int[] a) { return Args(c, CODE_NATIVE_FUNCTION, a); }
		public static string NEGATIVE(Compiler c) { return None(c, CODE_NEGATIVE); }
		public static string NOOP(Compiler c) { return None(c, CODE_NOOP); }
		public static string NOT_EQUAL(Compiler c) { return None(c, CODE_NOT_EQUAL); }
		public static string NULL(Compiler c) { return None(c, CODE_NULL); }
		public static string POP(Compiler c) { return None(c, CODE_POP); }
		public static string RETURN(Compiler c) { return None(c, CODE_RETURN); }
		// Explicitly declare other int parameters (for readability)
		public static string STACK_DATA(Compiler c, int fileNameStringId, int methodSignatureStringId) { return Args(c, CODE_STACK_DATA, new int[] { fileNameStringId, methodSignatureStringId }); }
		public static string STRING(Compiler c, int a) { return Single(c, CODE_STRING, a); }
		public static string SUBTRACT(Compiler c) { return None(c, CODE_SUBTRACT); }
		public static string SWITCH(Compiler c, params int[] a) { return Args(c, CODE_SWITCH, a); }
		public static string THIS(Compiler c) { return None(c, CODE_THIS); }
		public static string THROW(Compiler c) { return None(c, CODE_THROW); }
		public static string THROW_BUBBLING(Compiler c) { return None(c, CODE_THROW_BUBBLING); }
		public static string TRUE(Compiler c) { return None(c, CODE_TRUE); }
		public static string TRY_POP(Compiler c) { return None(c, CODE_TRY_POP); }
		public static string TRY_PUSH(Compiler c, params int[] a) { return Args(c, CODE_TRY_PUSH, a); }
		public static string VARIABLE(Compiler c, int a) { return Single(c, CODE_VARIABLE, a); }
		public static string VARIABLE_WRAPPED(Compiler c, int a) { return Single(c, CODE_VARIABLE_WRAPPED, a); }
		public static string XOR(Compiler c) { return None(c, CODE_XOR); }

		private static string Args(Compiler compiler, string[] codes, int[] args)
		{
			return Compute(compiler, codes, args);
		}

		private static string Single(Compiler compiler, string[] codes, int arg)
		{
			return Compute(compiler, codes, new int[] { arg });
		}

		private static string None(Compiler compiler, string[] codes)
		{
			return Compute(compiler, codes, null);
		}

		private static string Compute(Compiler compiler, string[] codes, int[] args)
		{
			string output = codes[compiler.IsReadable ? 1 : 0];

			if (args == null) return output;

			for (int i = 0; i < args.Length; ++i)
			{
				if (i > 0)
				{
					output += compiler.IsReadable ? " " : ",";
				}
				output += Util.ToHex(compiler, args[i]);
			}
			if (!compiler.IsReadable)
			{
				output += "|";
			}
			return output;
		}

		private static string[] Reg(string code, string readableCode)
		{
			if (registry.Contains(code))
				throw new Exception("You've used the same code for multiple commands.");
			registry.Add(code);

			if (readableRegistry.Contains(readableCode))
				throw new Exception("You've used the same code for multiple commands.");
			readableRegistry.Add(readableCode);

			readableCode += " ";

			return new string[] { code, readableCode };
		}
	}
}
